package cc.rengu.igas.channel.wechat.core.service.trans;


import cc.rengu.igas.channel.wechat.common.constant.WechatConfigConstant;
import cc.rengu.igas.channel.wechat.common.constant.WechatParamConstant;
import cc.rengu.igas.channel.wechat.common.constant.WechatTreeNodeConstant;
import cc.rengu.igas.channel.wechat.common.enums.RespCodeEnum;
import cc.rengu.igas.channel.wechat.core.model.ChnlBizResponse;
import cc.rengu.igas.channel.wechat.core.realize.WechatSignService;
import cc.rengu.igas.channel.wechat.core.realize.impl.WechatSignServiceImpl;
import cc.rengu.igas.channel.wechat.core.service.base.MchntService;
import cc.rengu.igas.channel.wechat.facade.request.WechatUploadImageRequest;
import cc.rengu.igas.channel.wechat.facade.response.WechatUploadImageResponse;
import cc.rengu.oltp.service.common.dao.CertInfoMapper;
import cc.rengu.oltp.service.common.dao.KeyInfoMapper;
import cc.rengu.oltp.service.common.dao.SecPlanInfoMapper;
import cc.rengu.oltp.service.common.dao.impl.CertInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.KeyInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.SecPlanInfoMapperImpl;
import cc.rengu.oltp.service.common.entity.*;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.realize.SysParamService;
import cc.rengu.oltp.service.realize.impl.SysParamServiceImpl;
import cc.rengu.oltp.utility.util.*;
import cc.rengu.utility.cache.UnifiedCache;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;


/**
 * 图片上传申请
 */
public class ImageUploadService extends MchntService {

    @Override
    protected Object beforeBizProcess() throws Exception {
        WechatUploadImageRequest uploadImageRequest = new WechatUploadImageRequest();
        ConvertUtil.convertOutput(uploadImageRequest);
        return uploadImageRequest;
    }

    @Override
    protected ChnlBizResponse callBizService(Object request) throws Exception {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        int iReturnCode = 0;

        WechatUploadImageResponse uploadImageResponse = new WechatUploadImageResponse();
        WechatUploadImageRequest uploadImageRequest = (WechatUploadImageRequest) request;

        DstChannelCfg dstChannelCfg = (DstChannelCfg) xmlTreeUtil.getXmlTreeObjectValue(WechatTreeNodeConstant.DST_CHANNEL_CFG, DstChannelCfg.class);
        DstChannelInfo dstChannelInfo = (DstChannelInfo) xmlTreeUtil.getXmlTreeObjectValue(WechatTreeNodeConstant.DST_CHANNEL_INFO, DstChannelInfo.class);

        File imageFile = new File(uploadImageRequest.getFilePath());
        if (!imageFile.exists()) {
            rglog.error("上传的图片文件<{}>不存在", uploadImageRequest.getFilePath());
            throw new BizException(RespCodeEnum.IMAGE_FILE_NOT_EXIST.getRespCode(), RespCodeEnum.IMAGE_FILE_NOT_EXIST.getRespDesc());
        }
        String imageId = uploadImage(imageFile, uploadImageRequest, dstChannelCfg);
        if (null == imageId) {
            rglog.error("调用微信上传图片失败");
            throw new BizException(RespCodeEnum.SYSTEM_ERROR.getRespCode(), RespCodeEnum.SYSTEM_ERROR.getRespDesc());
        }
        ConvertUtil.convertOutput(uploadImageResponse);
        uploadImageResponse.setMediaId(imageId);
        uploadImageResponse.setRespCode(RespCodeEnum.TRANS_SUCCESS.getRespCode());
        uploadImageResponse.setRespDesc(RespCodeEnum.TRANS_SUCCESS.getRespDesc());
        ChnlBizResponse chnlBizResponse = new ChnlBizResponse();
        chnlBizResponse.setResult(uploadImageResponse);
        return chnlBizResponse;
    }

    @Override
    protected void afterBizProcess(ChnlBizResponse response) throws Exception {
        WechatUploadImageResponse uploadImageResponse = (WechatUploadImageResponse) response.getResult();
        ConvertUtil.convertInput(uploadImageResponse);
    }


    private String uploadImage(File file, WechatUploadImageRequest uploadImageRequest, DstChannelCfg dstChannelCfg) throws Exception {
        String instId = uploadImageRequest.getHeader().getInstId();
        int lastIndex = uploadImageRequest.getFilePath().lastIndexOf("/");
        String fileName = uploadImageRequest.getFilePath().substring(lastIndex + 1);
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParam = sysParamService.getSysParamInfo(uploadImageRequest.getHeader().getInstId(),
                WechatConfigConstant.WECHAT_CONF_NODE, WechatConfigConstant.WECHAT_IMAGE_UPLOAD_URL);
        if (sysParam == null) {
            rglog.error("获取微信图片上传url失败，请配置");
            throw new BizException(RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespCode(), RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespDesc());
        }
        String uploadUrl = sysParam.getParamValue();
        CertInfo certInfo = null;
        CertInfo pubCertInfo = null;
        /* 根据通道安全计划获取安全计划 */
        SecPlanInfoMapper secPlanInfoMapper = new SecPlanInfoMapperImpl();
        CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
        List<SecPlanInfo> secPlanInfoList = secPlanInfoMapper.selectSecPlanInfoBySecPlanId(instId, dstChannelCfg.getSecPlanId());
        if (null != secPlanInfoList && !secPlanInfoList.isEmpty()) {
            Optional<SecPlanInfo> secPlanInfoOpt = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(WechatParamConstant.V3_SIGN_CERT_TYPE)).findFirst();
            if (secPlanInfoOpt.isPresent()) {
                certInfo = certInfoMapper.selectCertInfoByPrimaryKey(instId + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType());
            }

            Optional<SecPlanInfo> secPlanInfoOptPub = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(WechatParamConstant.V3_VERIFY_CERT_TYPE)).findFirst();
            if (secPlanInfoOptPub.isPresent()) {
                pubCertInfo = certInfoMapper.selectCertInfoByPrimaryKey(instId + secPlanInfoOptPub.get().getSecPlanId() + secPlanInfoOptPub.get().getSecPlanType());
            }
        }
        if (certInfo == null || pubCertInfo == null) {
            rglog.error("微信服务商[{}]未配置加密公钥", uploadImageRequest.getChannelType());
            throw new BizException(RespCodeEnum.PUB_CERT_NOT_EXIST.getRespCode(), RespCodeEnum.PUB_CERT_NOT_EXIST.getRespDesc());
        }
        // 换行符
        String LINE_END = "\r\n";
        String PREFIX = "--";
        // 定义数据分隔线
        String BOUNDARY = UUID.randomUUID().toString().replaceAll("-", "");
        //时间戳
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        //随机数
        String nonce_str = RandomUtil.randomString(32);

        String fileSha256 = getFileSHA256(file);
        //拼签名串
        StringBuffer sb = new StringBuffer();
        sb.append("POST").append("\n");
        sb.append(uploadUrl.substring(uploadUrl.indexOf("/v3"), uploadUrl.length())).append("\n");
        sb.append(timestamp).append("\n");
        sb.append(nonce_str).append("\n");
        sb.append("{\"filename\":\"" + fileName + "\",\"sha256\":\"" + fileSha256 + "\"}").append("\n");
        rglog.info("签名原串:<{}>", sb.toString());

        WechatSignService wechatSignService = new WechatSignServiceImpl();
        String signData = wechatSignService.generateSignString(sb.toString(), certInfo);

        //拼装http头的Authorization内容
        String authorization = "WECHATPAY2-SHA256-RSA2048 mchid=\"" + dstChannelCfg.getProviderMchntNo() + "\",nonce_str=\"" + nonce_str + "\",signature=\"" + signData + "\",timestamp=\"" + timestamp + "\",serial_no=\"" + certInfo.getCertId() + "\"";
        rglog.debug("authorization值:" + authorization);

        //接口URL
        URL url = new URL(uploadUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // 设置为POST
        conn.setRequestMethod("POST");
        // 发送POST请求必须设置如下两行
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setUseCaches(false);
        // 设置请求头参数
        conn.setRequestProperty("Charsert", "UTF-8");
        conn.setRequestProperty("Accept", "application/json");
        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
        conn.setRequestProperty("Authorization", authorization);

        OutputStream outputStream = null;
        InputStream is = null;
        DataOutputStream dos = null;
        try {
            outputStream = conn.getOutputStream();
            dos = new DataOutputStream(outputStream);

            //拼装请求内容第一部分
            StringBuilder strSb = new StringBuilder();
            strSb.append(PREFIX).append(BOUNDARY).append(LINE_END)
                    .append("Content-Disposition: form-data; name=\"meta\";" + LINE_END)
                    .append("Content-Type: application/json; " + LINE_END)
                    .append(LINE_END)// 空行
                    .append("{\"filename\":\"" + fileName + "\",\"sha256\":\"" + fileSha256 + "\"}")
                    .append(LINE_END);
            dos.write(strSb.toString().getBytes());

            dos.flush();

            //拼装请求内容第二部分
            StringBuilder fileSbStart = new StringBuilder();
            fileSbStart.append(PREFIX).append(BOUNDARY).append(LINE_END)
                    .append("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\";" + LINE_END)
                    .append("Content-Type: image/jpeg" + LINE_END)
                    .append(LINE_END);// 空行
            dos.write(fileSbStart.toString().getBytes());

            dos.flush();

            //文件二进制内容
            is = new FileInputStream(file);
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = is.read(buffer)) != -1) {
                dos.write(buffer, 0, len);
            }

            //拼装请求内容结尾
            StringBuilder fileSbEnd = new StringBuilder();
            fileSbEnd.append(LINE_END)
                    .append(PREFIX).append(BOUNDARY).append(PREFIX)
                    .append(LINE_END);

            dos.write(fileSbEnd.toString().getBytes());

            dos.flush();
        } catch (IOException e) {
            rglog.error(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
            if (dos != null) {
                try {
                    dos.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
        }
        //接收返回
        //打印返回头信息
        rglog.debug("接口返回头信息:");
        Map<String, List<String>> responseHeader = conn.getHeaderFields();
        for (Map.Entry<String, List<String>> entry : responseHeader.entrySet()) {
            rglog.debug(entry.getKey() + ":" + entry.getValue());
        }
        String media_id = "";
        //打印返回内容
        int responseCode = conn.getResponseCode();
        String rescontent = "";
        InputStream inputStream = null;
        try {
            if ((String.valueOf(responseCode)).startsWith("2")) {
                //成功
                inputStream = conn.getInputStream();
                rescontent = new String(InputStreamTOByte(inputStream));
                rglog.debug("图片上传成功:" + rescontent);
                JSONObject json = JSONObject.parseObject(rescontent);
                media_id = json.getString("media_id");
            } else {
                //失败
                inputStream = conn.getErrorStream();
                rescontent = new String(InputStreamTOByte(inputStream));
                rglog.debug("图片上传失败:" + rescontent);
            }
        } catch (Exception e) {
            rglog.error(e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
        }

        //微信公众平台公钥
        String serialNo = responseHeader.get("Wechatpay-Serial").get(0);
        if (serialNo != null && !StringUtil.isEmptyOrNull(pubCertInfo.getCertDn()) && !pubCertInfo.getCertDn().equals(serialNo)) {
            rglog.error("微信证书序列号和本地序列号不一致，请检查微信平台证书");
            /*异步更新平台公钥*/
            UnifiedCache.remove("WECHATV3PLAT", pubCertInfo.getCertDn());
            //请求公钥证书
            getV3WechatPlatCert(instId, certInfo, dstChannelCfg, pubCertInfo);

            return media_id;
        }
        SysParam sysParam1 = sysParamService.getSysParamInfo(instId, "wechat", "v3_check_sign_flag");
        if (null != sysParam1) {
            //验证微信支付返回签名
            String Wtimestamp = responseHeader.get("Wechatpay-Timestamp").get(0);
            String Wnonce = responseHeader.get("Wechatpay-Nonce").get(0);
            String sign = responseHeader.get("Wechatpay-Signature").get(0);
            //拼装待签名串
            StringBuilder ss = new StringBuilder();
            ss.append(Wtimestamp).append("\n");
            ss.append(Wnonce).append("\n");
            ss.append(rescontent).append("\n");
            //读取微信平台公钥
            if (!wechatSignService.verifySignString(sign, ss.toString(), pubCertInfo)) {
                return null;
            } else {
                return media_id;
            }
        }
        return media_id;
    }

    private void getV3WechatPlatCert(String instId, CertInfo certInfo, DstChannelCfg dstChannelCfg, CertInfo pubCertInfo) throws Exception {
        SysParamService sysParamService = new SysParamServiceImpl();
        SysParam sysParam = sysParamService.getSysParamInfo(instId, "wechatV3", "proxyHost");
        if (null == sysParam) {
            rglog.error("未配置微信V3接口代理服务器,wechatV3,proxyHost");
            throw new BizException(RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespCode(), RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespDesc());
        }
        String proxyHost = sysParam.getParamValue();
        sysParam = sysParamService.getSysParamInfo(instId, "wechatV3", "proxyPort");
        if (null == sysParam) {
            rglog.error("未配置微信V3接口代理服务器,wechatV3,proxyPort");
            throw new BizException(RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespCode(), RespCodeEnum.CONFIG_PARAM_NOT_EXIST.getRespDesc());
        }
        int proxyPort = Integer.valueOf(sysParam.getParamValue()).intValue();
        // 换行符
        String LINE_END = "\r\n";
        String PREFIX = "--";
        // 定义数据分隔线
        String BOUNDARY = UUID.randomUUID().toString().replaceAll("-", "");
        //时间戳
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        //随机数
        String nonce_str = RandomUtil.randomString(32);

        //拼签名串
        StringBuffer sb = new StringBuffer();
        sb.append("GET").append("\n");
        sb.append("/v3/certificates").append("\n");
        sb.append(timestamp).append("\n");
        sb.append(nonce_str).append("\n");
        sb.append("\n");
        rglog.info("签名原串:<{}>", sb.toString());

        WechatSignService wechatSignService = new WechatSignServiceImpl();
        String signData = wechatSignService.generateSignString(sb.toString(), certInfo);

        //拼装http头的Authorization内容
        String authorization = "WECHATPAY2-SHA256-RSA2048 mchid=\"" + dstChannelCfg.getProviderMchntNo() + "\",nonce_str=\"" + nonce_str + "\",signature=\"" + signData + "\",timestamp=\"" + timestamp + "\",serial_no=\"" + certInfo.getCertDn() + "\"";
        rglog.debug("authorization值:" + authorization);
        String certDownloadUrl = "https://api.mch.weixin.qq.com/v3/certificates";
        //接口URL
        URL url = new URL(certDownloadUrl);

        InetSocketAddress addr = new InetSocketAddress(proxyHost, proxyPort);
        Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
        // 设置为POST
        conn.setRequestMethod("GET");
        // 发送POST请求必须设置如下两行
        conn.setUseCaches(false);
        // 设置请求头参数
        conn.setRequestProperty("Accept", "application/json");
        conn.setRequestProperty("Authorization", authorization);
        conn.setRequestProperty("User-Agent", "https://zh.wilipedia.org/wiki/User_agent");

        OutputStream outputStream = null;
        InputStream is = null;
        try {
            outputStream = conn.getOutputStream();
        } catch (IOException e) {
            rglog.error(e.getMessage());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
        }
        //接收返回
        //打印返回头信息
        rglog.debug("接口返回头信息:");
        Map<String, List<String>> responseHeader = conn.getHeaderFields();
        for (Map.Entry<String, List<String>> entry : responseHeader.entrySet()) {
            rglog.debug(entry.getKey() + ":" + entry.getValue());
        }
        String data = "";
        //打印返回内容
        int responseCode = conn.getResponseCode();
        String rescontent = "";
        InputStream inputStream = null;
        try {
            if ((String.valueOf(responseCode)).startsWith("2")) {
                //成功
                inputStream = conn.getInputStream();
                rescontent = new String(InputStreamTOByte(inputStream));
                rglog.debug("证书下载成功:" + rescontent);
                JSONObject json = JSONObject.parseObject(rescontent);
                JSONArray dataArray = json.getJSONArray("data");
                //解密微信平台证书，并更新
                /* 根据通道安全计划获取安全计划 */
                SecPlanInfoMapper secPlanInfoMapper = new SecPlanInfoMapperImpl();
                KeyInfoMapper keyInfoMapper = new KeyInfoMapperImpl();
                KeyInfo keyInfo = new KeyInfo();
                List<SecPlanInfo> secPlanInfoList = secPlanInfoMapper.selectSecPlanInfoBySecPlanId(instId, dstChannelCfg.getSecPlanId());
                if (null != secPlanInfoList && !secPlanInfoList.isEmpty()) {
                    Optional<SecPlanInfo> secPlanInfoOpt = secPlanInfoList.stream().filter(item -> item.getSecPlanType().equals(WechatParamConstant.V3_AES_KEY_TYPE)).findFirst();
                    if (secPlanInfoOpt.isPresent()) {
                        keyInfo = keyInfoMapper.selectKeyInfoByPrimaryKey(instId + secPlanInfoOpt.get().getSecPlanId() + secPlanInfoOpt.get().getSecPlanType());
                    }
                }
                JSONObject allCertInfo = (JSONObject) dataArray.get(0);
                String serialNo = allCertInfo.getString("serial_no");
                rglog.info("serialNo = <{}>", serialNo);
                JSONObject encryptCert = allCertInfo.getJSONObject("encrypt_certificate");
                rglog.info("encryptCert = <{}>", encryptCert);
                String nonce = encryptCert.getString("nonce");
                rglog.info("nonce = <{}>", nonce);
                String associatedData = encryptCert.getString("associated_data");
                rglog.info("associatedData = <{}>", associatedData);
                String cipherText = encryptCert.getString("ciphertext");
                rglog.info("cipherText = <{}>", cipherText);
                rglog.debug("keyInfo = <{}>", JSON.toJSONString(keyInfo));
                String apiV3AesSecret = keyInfo.getKeyValue();
                String wechatPlatCertValue = AESUtil.gcmDecryptToString(apiV3AesSecret.getBytes(), associatedData.getBytes(), nonce.getBytes(), cipherText);
                //更新到微信平台证书中
                if (!StringUtil.isEmptyOrNull(wechatPlatCertValue)) {
                    rglog.debug("wechatPlatCertValue = <{}>", wechatPlatCertValue);
                    //需处理wechatPlatCertValue值得证书头和尾
                    // wechatPlatCertValue = wechatPlatCertValue.substring(wechatPlatCertValue.indexOf("\n"),wechatPlatCertValue.lastIndexOf("\n")).replaceAll("\n","");
                    //将证书值写到本地路径中从本地路径获取
                    CertificateFactory cf = CertificateFactory.getInstance("X509");
                    ByteArrayInputStream inputStream1 = new ByteArrayInputStream(wechatPlatCertValue.getBytes(StandardCharsets.UTF_8));
                    Certificate certificate = null;
                    try {
                        certificate = cf.generateCertificate(inputStream1);
                    } catch (CertificateException ce) {
                        ce.printStackTrace();
                    }
                    UnifiedCache.set("WECHATV3PLAT", serialNo, certificate);
                    CertInfoMapper certInfoMapper = new CertInfoMapperImpl();
                    pubCertInfo.setCertValue(wechatPlatCertValue);
                    pubCertInfo.setCertDn(serialNo);
                    certInfoMapper.updateCertInfoByPrimaryKey(pubCertInfo);
                }
            } else {
                //失败
                inputStream = conn.getErrorStream();
                rescontent = new String(InputStreamTOByte(inputStream));
                rglog.debug("证书下载失败:" + rescontent);
            }
        } catch (Exception e) {
            rglog.error(e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    rglog.error(e.getMessage());
                }
            }
        }

    }

    /**
     * 计算文件摘要
     *
     * @param file
     * @return
     */
    private String getFileSHA256(File file) {
        MessageDigest digest = null;
        FileInputStream in = null;
        byte[] buffer = new byte[1024];
        int len = -1;
        try {
            digest = MessageDigest.getInstance("SHA-256");
            in = new FileInputStream(file);
            while ((len = in.read(buffer, 0, 1024)) != -1) {
                digest.update(buffer, 0, len);
            }
            in.close();
            return ByteUtil.bytesToHexString(digest.digest());
        } catch (Exception e) {
            rglog.error(e.getMessage());
            return null;
        } finally {
            if (null != in) {
                try {
                    in.close();
                } catch (IOException e) {
                    rglog.error(e.getMessage());
                }
            }
        }
    }

    public static byte[] InputStreamTOByte(InputStream in) throws IOException {

        int BUFFER_SIZE = 4096;
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] data = new byte[BUFFER_SIZE];
        int count = -1;

        while ((count = in.read(data, 0, BUFFER_SIZE)) != -1) {
            outStream.write(data, 0, count);
        }

        data = null;
        byte[] outByte = outStream.toByteArray();
        try {
            if (outStream != null) {
                outStream.close();
            }
        } catch (Exception e) {

        }


        return outByte;
    }


}
